|
|
@@ -0,0 +1,177 @@
|
|
1
|
+require "twitter"
|
|
2
|
+
|
|
3
|
+module Agents
|
|
4
|
+ class TwitterUserAgent < Agent
|
|
5
|
+ cannot_receive_events!
|
|
6
|
+
|
|
7
|
+ description <<-MD
|
|
8
|
+ The TwitterUserAgent follows the timeline of a specified Twitter user.
|
|
9
|
+
|
|
10
|
+ You must set up a Twitter app and provide it's `consumer_key`, `consumer_secret`, `oauth_token` and `oauth_token_secret`, (Also shown as "Access token" on the Twitter developer's site.) along with the `username` of the Twitter user to monitor.
|
|
11
|
+
|
|
12
|
+ Set `expected_update_period_in_days` to the maximum amount of time that you'd expect to pass between Events being created by this Agent.
|
|
13
|
+ MD
|
|
14
|
+
|
|
15
|
+ event_description <<-MD
|
|
16
|
+ Events are the raw JSON provided by the Twitter API. Should look something like:
|
|
17
|
+
|
|
18
|
+ {
|
|
19
|
+ :created_at=>"Thu Apr 04 13:27:48 +0000 2013",
|
|
20
|
+ :id=>319803490421596160,
|
|
21
|
+ :id_str=>"319803490421596160",
|
|
22
|
+ :text=>
|
|
23
|
+ "In which @jeresig goes to an art gallery and is \"the JavaScript programmer\". http://t.co/gt3PT1d3G1",
|
|
24
|
+ :source=>
|
|
25
|
+ "<a href=\"http://itunes.apple.com/us/app/twitter/id409789998?mt=12\" rel=\"nofollow\">Twitter for Mac</a>",
|
|
26
|
+ :truncated=>false,
|
|
27
|
+ :in_reply_to_status_id=>nil,
|
|
28
|
+ :in_reply_to_status_id_str=>nil,
|
|
29
|
+ :in_reply_to_user_id=>nil,
|
|
30
|
+ :in_reply_to_user_id_str=>nil,
|
|
31
|
+ :in_reply_to_screen_name=>nil,
|
|
32
|
+ :user=>
|
|
33
|
+ {:id=>2341001,
|
|
34
|
+ :id_str=>"2341001",
|
|
35
|
+ :name=>"Albert Sun",
|
|
36
|
+ :screen_name=>"albertsun",
|
|
37
|
+ :location=>"New York, NY",
|
|
38
|
+ :description=>
|
|
39
|
+ "News apps developer at NYT, formerly WSJ. graduated Penn 2010, geek, journalist, data-viz, nlp, gis, digital economics =)",
|
|
40
|
+ :url=>"http://albertsun.info",
|
|
41
|
+ :entities=>
|
|
42
|
+ {:url=>
|
|
43
|
+ {:urls=>
|
|
44
|
+ [{:url=>"http://albertsun.info",
|
|
45
|
+ :expanded_url=>nil,
|
|
46
|
+ :indices=>[0, 21]}]},
|
|
47
|
+ :description=>{:urls=>[]}},
|
|
48
|
+ :protected=>false,
|
|
49
|
+ :followers_count=>1857,
|
|
50
|
+ :friends_count=>798,
|
|
51
|
+ :listed_count=>115,
|
|
52
|
+ :created_at=>"Mon Mar 26 19:22:05 +0000 2007",
|
|
53
|
+ :favourites_count=>9,
|
|
54
|
+ :utc_offset=>-18000,
|
|
55
|
+ :time_zone=>"Eastern Time (US & Canada)",
|
|
56
|
+ :geo_enabled=>false,
|
|
57
|
+ :verified=>false,
|
|
58
|
+ :statuses_count=>2572,
|
|
59
|
+ :lang=>"en",
|
|
60
|
+ :contributors_enabled=>false,
|
|
61
|
+ :is_translator=>false,
|
|
62
|
+ :profile_background_color=>"1B2A2B",
|
|
63
|
+ :profile_background_image_url=>
|
|
64
|
+ "http://a0.twimg.com/profile_background_images/2802438/twitterbg.jpg",
|
|
65
|
+ :profile_background_image_url_https=>
|
|
66
|
+ "https://si0.twimg.com/profile_background_images/2802438/twitterbg.jpg",
|
|
67
|
+ :profile_background_tile=>false,
|
|
68
|
+ :profile_image_url=>
|
|
69
|
+ "http://a0.twimg.com/profile_images/110500205/profile-square_normal.jpg",
|
|
70
|
+ :profile_image_url_https=>
|
|
71
|
+ "https://si0.twimg.com/profile_images/110500205/profile-square_normal.jpg",
|
|
72
|
+ :profile_link_color=>"0000FF",
|
|
73
|
+ :profile_sidebar_border_color=>"87BC44",
|
|
74
|
+ :profile_sidebar_fill_color=>"E0FF92",
|
|
75
|
+ :profile_text_color=>"000000",
|
|
76
|
+ :profile_use_background_image=>true,
|
|
77
|
+ :default_profile=>false,
|
|
78
|
+ :default_profile_image=>false,
|
|
79
|
+ :following=>false,
|
|
80
|
+ :follow_request_sent=>false,
|
|
81
|
+ :notifications=>false},
|
|
82
|
+ :geo=>nil,
|
|
83
|
+ :coordinates=>nil,
|
|
84
|
+ :place=>nil,
|
|
85
|
+ :contributors=>nil,
|
|
86
|
+ :retweet_count=>0,
|
|
87
|
+ :favorite_count=>0,
|
|
88
|
+ :entities=>
|
|
89
|
+ {:hashtags=>[],
|
|
90
|
+ :urls=>
|
|
91
|
+ [{:url=>"http://t.co/gt3PT1d3G1",
|
|
92
|
+ :expanded_url=>
|
|
93
|
+ "http://www.nytimes.com/2013/04/04/fashion/art-and-techology-a-clash-of-cultures.html?pagewanted=all",
|
|
94
|
+ :display_url=>"nytimes.com/2013/04/04/fas",
|
|
95
|
+ :indices=>[77, 99]}],
|
|
96
|
+ :user_mentions=>
|
|
97
|
+ [{:screen_name=>"jeresig",
|
|
98
|
+ :name=>"John Resig",
|
|
99
|
+ :id=>752673,
|
|
100
|
+ :id_str=>"752673",
|
|
101
|
+ :indices=>[9, 17]}]},
|
|
102
|
+ :favorited=>false,
|
|
103
|
+ :retweeted=>false,
|
|
104
|
+ :possibly_sensitive=>false,
|
|
105
|
+ :lang=>"en"
|
|
106
|
+ }
|
|
107
|
+ MD
|
|
108
|
+
|
|
109
|
+ default_schedule "every_1h"
|
|
110
|
+
|
|
111
|
+ def validate_options
|
|
112
|
+ unless options[:username].present? && options[:expected_update_period_in_days].present? && options[:consumer_key].present? && options[:consumer_secret].present? && options[:oauth_token].present? && options[:oauth_token_secret].present?
|
|
113
|
+ errors.add(:base, "expected_update_period_in_days, username, consumer_key, consumer_secret, oauth_token and oauth_token_secret are required")
|
|
114
|
+ end
|
|
115
|
+ end
|
|
116
|
+
|
|
117
|
+ def working?
|
|
118
|
+ (event = event_created_within(options[:expected_update_period_in_days].to_i.days)) && event.payload.present?
|
|
119
|
+ end
|
|
120
|
+
|
|
121
|
+ def default_options
|
|
122
|
+ {
|
|
123
|
+ :username => "tectonic",
|
|
124
|
+ :expected_update_period_in_days => "2",
|
|
125
|
+ :consumer_key => "---",
|
|
126
|
+ :consumer_secret => "---",
|
|
127
|
+ :oauth_token => "---",
|
|
128
|
+ :oauth_token_secret => "---"
|
|
129
|
+ }
|
|
130
|
+ end
|
|
131
|
+
|
|
132
|
+ # def process_tweet(filter, status)
|
|
133
|
+ # if options[:generate] == "counts"
|
|
134
|
+ # # Avoid memory pollution
|
|
135
|
+ # me = Agent.find(id)
|
|
136
|
+ # me.memory[:filter_counts] ||= {}
|
|
137
|
+ # me.memory[:filter_counts][filter.to_sym] ||= 0
|
|
138
|
+ # me.memory[:filter_counts][filter.to_sym] += 1
|
|
139
|
+ # me.save!
|
|
140
|
+ # else
|
|
141
|
+ # create_event :payload => status.merge(:filter => filter.to_s)
|
|
142
|
+ # end
|
|
143
|
+ # end
|
|
144
|
+
|
|
145
|
+ def check
|
|
146
|
+ Twitter.configure do |config|
|
|
147
|
+ config.consumer_key = options[:consumer_key]
|
|
148
|
+ config.consumer_secret = options[:consumer_secret]
|
|
149
|
+ config.oauth_token = options[:oauth_token]
|
|
150
|
+ config.oauth_token_secret = options[:oauth_token_secret]
|
|
151
|
+ end
|
|
152
|
+
|
|
153
|
+ since_id = memory[:since_id] || nil
|
|
154
|
+ max_id = memory[:max_id] || nil
|
|
155
|
+ opts = {:count => 200, :include_rts => true, :exclude_replies => false, :include_entities => true, :contributor_details => true}
|
|
156
|
+ opts.merge! :since_id => since_id unless since_id.nil?
|
|
157
|
+ opts.merge! :max_id => max_id unless max_id.nil?
|
|
158
|
+
|
|
159
|
+ tweets = Twitter.user_timeline(options[:username], opts)
|
|
160
|
+
|
|
161
|
+ tweets.each do |tweet|
|
|
162
|
+ memory[:since_id] = tweet.id if !memory[:since_id] || (tweet.id > memory[:since_id])
|
|
163
|
+
|
|
164
|
+ create_event :payload => tweet.attrs
|
|
165
|
+ end
|
|
166
|
+
|
|
167
|
+ save!
|
|
168
|
+ # if memory[:filter_counts] && memory[:filter_counts].length > 0
|
|
169
|
+ # memory[:filter_counts].each do |filter, count|
|
|
170
|
+ # create_event :payload => { :filter => filter.to_s, :count => count, :time => Time.now.to_i }
|
|
171
|
+ # end
|
|
172
|
+ # memory[:filter_counts] = {}
|
|
173
|
+ # save!
|
|
174
|
+ # end
|
|
175
|
+ end
|
|
176
|
+ end
|
|
177
|
+end
|